--- START OF FILE base.py ---

# Import necessary modules from the standard library and third-party packages.
import datetime
import functools
import logging
import traceback
from typing import Any

# Import simplejson for efficient JSON handling.
import simplejson as json
# Import yaml for YAML serialization.
import yaml
# Import various components from Flask for web application development.
from flask import (
    Response,
    abort,
    flash,
    g,
    get_flashed_messages,
    jsonify,
    make_response,
    redirect,
    request,
    session,
    url_for,
)
# Import ModelView from Flask-AppBuilder, which is a base view for CRUD operations on a model.
from flask_appbuilder import ModelView
# Import compatibility utility from Flask-AppBuilder.
from flask_appbuilder._compat import as_unicode
# Import action decorator for creating custom actions in views.
from flask_appbuilder.actions import action
# Import base view classes from Flask-AppBuilder.
from flask_appbuilder.baseviews import BaseCRUDView, BaseView, expose
# Import constants from Flask-AppBuilder for standardized messages and prefixes.
from flask_appbuilder.const import (
    FLAMSG_ERR_SEC_ACCESS_DENIED,
    LOGMSG_ERR_SEC_ACCESS_DENIED,
    PERMISSION_PREFIX,
)
# Import form utilities from Flask-AppBuilder.
from flask_appbuilder.forms import DynamicForm, GeneralModelConverter
# Import model and URL tool utilities from Flask-AppBuilder.
from flask_appbuilder.models.sqla.filters import BaseFilter
from flask_appbuilder.urltools import (
    get_filter_args,
    get_order_args,
    get_page_args,
    get_page_size_args,
)
# Import widget classes from Flask-AppBuilder.
from flask_appbuilder.widgets import GroupFormListWidget, ListWidget
# Import internationalization and localization functions from Flask-Babel.
from flask_babel import get_locale, gettext as __, lazy_gettext as _
# Import the base form class from Flask-WTF.
from flask_wtf.form import FlaskForm
# Import OpenTelemetry for distributed tracing.
from opentelemetry import trace
# Import Werkzeug's HTTPException for handling HTTP errors.
from werkzeug.exceptions import HTTPException
# Import WTForms fields.
from wtforms.fields.core import Field, UnboundField

# Import application-specific modules.
from myapp.app import conf, db, event_logger, security_manager
from myapp.forms import MySearchWidget
from myapp.utils import core

# Define a tuple of configuration keys that should be exposed to the frontend.
FRONTEND_CONF_KEYS = (
    'MYAPP_WEBSERVER_TIMEOUT',
    'ENABLE_JAVASCRIPT_CONTROLS',
    'MYAPP_WEBSERVER_DOMAINS',
)


# Initialize a tracer for OpenTelemetry.
tracer = trace.get_tracer(__name__)


def has_access(f):
    """
    Use this decorator to enable granular security permissions to your methods.
    Permissions will be associated to a role, and roles are associated to users.

    By default the permission's name is the methods name.
    """
    # Determine the permission name, using a custom one if defined.
    if hasattr(f, '_permission_name'):
        permission_str = f._permission_name
    else:
        permission_str = f.__name__

    # Define the wrapper function that will enforce the permission check.
    def wraps(self, *args, **kwargs):
        # Construct the full permission string with a prefix.
        permission_str = f'{PERMISSION_PREFIX}{f._permission_name}'
        # Check if a method-specific permission name is defined.
        if self.method_permission_name:
            _permission_name = self.method_permission_name.get(f.__name__)
            if _permission_name:
                permission_str = f'{PERMISSION_PREFIX}{_permission_name}'
        # Check if the user has the required permission.
        if permission_str in self.base_permissions and self.appbuilder.sm.has_access(
            permission_str, self.class_permission_name
        ):
            # If access is granted, execute the original function.
            return f(self, *args, **kwargs)
        else:
            # If access is denied, log the attempt and flash a message.
            logging.warning(
                LOGMSG_ERR_SEC_ACCESS_DENIED.format(permission_str, self.__class__.__name__)
            )
            flash(as_unicode(FLAMSG_ERR_SEC_ACCESS_DENIED), 'danger')
        # Redirect the user to the login page if access is denied.
        return redirect(
            url_for(self.appbuilder.sm.auth_view.__class__.__name__ + '.login', next=request.url)
        )

    # Store the permission name on the function object for introspection.
    f._permission_name = permission_str
    # Return the wrapped function, preserving the original function's metadata.
    return functools.update_wrapper(wraps, f)


def has_access_api(f):
    """
    Use this decorator to enable granular security permissions to your API methods.
    Permissions will be associated to a role, and roles are associated to users.

    By default the permission's name is the methods name.

    this will return a message and HTTP 401 is case of unauthorized access.
    """
    # Determine the permission name, using a custom one if defined.
    if hasattr(f, '_permission_name'):
        permission_str = f._permission_name
    else:
        permission_str = f.__name__

    # Define the wrapper function that will enforce the permission check for APIs.
    def wraps(self, *args, **kwargs):
        # Construct the full permission string.
        permission_str = f'{PERMISSION_PREFIX}{f._permission_name}'
        # Check for a method-specific permission name override.
        if self.method_permission_name:
            _permission_name = self.method_permission_name.get(f.__name__)
            if _permission_name:
                permission_str = f'{PERMISSION_PREFIX}{_permission_name}'
        # Check if the user has the required permission.
        if permission_str in self.base_permissions and self.appbuilder.sm.has_access(
            permission_str, self.class_permission_name
        ):
            # If access is granted, execute the original function.
            return f(self, *args, **kwargs)
        else:
            # If access is denied, log the attempt.
            logging.warning(
                LOGMSG_ERR_SEC_ACCESS_DENIED.format(permission_str, self.__class__.__name__)
            )
            # Create a JSON response with an unauthorized error message and a 401 status code.
            response = make_response(
                jsonify({'message': str(FLAMSG_ERR_SEC_ACCESS_DENIED), 'severity': 'danger'}), 401
            )
            response.headers['Content-Type'] = 'application/json'
            # Return the JSON error response.
            return response

    # Store the permission name on the function object.
    f._permission_name = permission_str
    # Return the wrapped function.
    return functools.update_wrapper(wraps, f)


# This function retrieves a formatted error message.
def get_error_msg():
    # If the configuration allows showing stack traces, format the exception.
    if conf.get('SHOW_STACKTRACE'):
        error_msg = traceback.format_exc()
    # Otherwise, return a generic fatal error message.
    else:
        error_msg = 'FATAL ERROR \n'
        error_msg += (
            'Stacktrace is hidden. Change the SHOW_STACKTRACE configuration setting to enable it'
        )
    return error_msg


# This function constructs a JSON response for errors.
def json_error_response(msg=None, status=500, stacktrace=None, payload=None, link=None):
    # If no payload is provided, create a default one with the error message and stack trace.
    if not payload:
        payload = {'error': f'{msg}'}
        payload['stacktrace'] = core.get_stacktrace()
    # If a link is provided, add it to the payload.
    if link:
        payload['link'] = link

    # Return a Flask Response object with the JSON payload.
    return Response(
        # Serialize the payload to JSON, handling datetime objects and NaNs.
        json.dumps(payload, default=core.json_iso_dttm_ser, ignore_nan=True),
        status=status,
        mimetype='application/json',
    )


# This function creates a generic JSON response.
def json_response(message, status, result):
    # Use Flask's jsonify to create the response.
    return jsonify({'message': message, 'status': status, 'result': result})


# This function creates a JSON success response.
def json_success(json_msg, status=200):
    # Return a Flask Response object with the JSON message.
    return Response(json_msg, status=status, mimetype='application/json')


# This function creates a data payload response, typically for APIs.
def data_payload_response(payload_json, has_error=False):
    # Set the status code to 400 if there is an error, otherwise 200.
    status = 400 if has_error else 200
    # Return a JSON success response with the appropriate status.
    return json_success(payload_json, status=status)


# 产生下载csv的响应header
def generate_download_headers(extension, filename=None):
    # Generate a filename based on the current timestamp if not provided.
    filename = filename if filename else datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
    # Set the Content-Disposition header to trigger a file download.
    content_disp = f'attachment; filename={filename}.{extension}'
    headers = {'Content-Disposition': content_disp}
    return headers


def api(f):
    """
    A decorator to label an endpoint as an API. Catches uncaught exceptions and
    return the response in the JSON format
    """

    # Define the wrapper function.
    def wraps(self, *args, **kwargs):
        try:
            # Try to execute the original API function.
            return f(self, *args, **kwargs)
        except Exception as e:
            # If any exception occurs, log it and return a JSON error response.
            logging.exception(e)
            return json_error_response(get_error_msg())

    # Return the wrapped function.
    return functools.update_wrapper(wraps, f)


def handle_api_exception(f):
    """
    A decorator to catch myapp exceptions. Use it after the @api decorator above
    so myapp exception handler is triggered before the handler for generic
    exceptions.
    """

    # Define the wrapper function.
    def wraps(self, *args, **kwargs):
        try:
            # Try to execute the original function.
            return f(self, *args, **kwargs)
        # Catch specific HTTP exceptions from Werkzeug.
        except HTTPException as e:
            logging.exception(e)
            # Return a JSON error response with details from the exception.
            return json_error_response(
                core.error_msg_from_exception(e),
                stacktrace=traceback.format_exc(),
                status=e.code,
            )
        # Catch all other exceptions.
        except Exception as e:
            logging.exception(e)
            # Return a generic JSON error response.
            return json_error_response(
                core.error_msg_from_exception(e), stacktrace=core.get_stacktrace()
            )

    # Return the wrapped function.
    return functools.update_wrapper(wraps, f)


# 获取用户的角色
def get_user_roles():
    # Check if the current user is anonymous.
    if g.user.is_anonymous:
        # Get the configured public role name.
        public_role = conf.get('AUTH_ROLE_PUBLIC')
        # Find and return the public role object if it exists.
        return [security_manager.find_role(public_role)] if public_role else []
    # If the user is authenticated, return their assigned roles.
    return g.user.roles


# A base view class for the application, inheriting from Flask-AppBuilder's BaseView.
class BaseMyappView(BaseView):
    # json响应
    # Method to create a standardized JSON response.
    def json_response(self, obj, status=200):
        return Response(
            # Serialize the object to JSON, using a custom serializer for datetime.
            json.dumps(obj, default=core.json_int_dttm_ser, ignore_nan=True),
            status=status,
            mimetype='application/json',
        )

    # 前端显示数据
    def common_bootstrap_payload(self):
        """Common data always sent to the client"""
        # Retrieve flashed messages (e.g., success or error notifications).
        messages = get_flashed_messages(with_categories=True)
        # Get the current locale for internationalization.
        locale = str(get_locale())
        # Return a dictionary containing common data for the frontend.
        return {
            'flash_messages': messages,
            # Expose specific configuration settings to the frontend.
            'conf': {k: conf.get(k) for k in FRONTEND_CONF_KEYS},
            'locale': locale,
            # Placeholders for language packs and feature flags.
            'language_pack': {},
            'feature_flags': {}
        }


# 自定义list页面
# Custom list widget that uses a specific template for rendering.
class MyappListWidget(ListWidget):
    # Override the default template path.
    template = 'myapp/fab_overrides/list.html'


# model 页面基本视图
# A base ModelView class with custom features for the application.
class MyappModelView(ModelView):
    # Defines the context of the API (web UI).
    api_type = 'web'
    # Placeholder for the data model manager.
    datamodel = None
    # Default number of items per page in list views.
    page_size = 100
    # Use the custom list widget.
    list_widget = MyappListWidget
    # Placeholder for the original model object being processed.
    src_item_object = None  # 原始model对象
    # Placeholder for the JSON representation of the original model object.
    src_item_json = {}  # 原始model对象的json
    # URL to redirect to after certain actions.
    check_redirect_list_url = None
    # Use a custom search widget.
    search_widget = MySearchWidget
    # URL for help documentation related to this view.
    help_url = ''

    # Hooks for custom logic before and after CRUD operations.
    pre_add_get = None
    pre_update_get = None
    post_list = None
    pre_show = None
    post_show = None
    check_edit_permission = None
    # Title for the view, used to generate other titles if they are not set.
    label_title = ''

    # Initialize a model converter for creating forms.
    conv = GeneralModelConverter(datamodel)
    # Pre-list hook.
    pre_list = None
    # Default user permissions for an item.
    user_permissions = {'can_add': True, 'can_edit': True, 'can_delete': True, 'can_show': True}

    # 建构响应体
    @staticmethod
    # @pysnooper.snoop()
    def response(code, **kwargs):
        """
            Generic HTTP JSON response method

        :param code: HTTP code (int)
        :param kwargs: Data structure for response (dict)
        :return: HTTP Json response
        """
        # 添flash的信息
        # Retrieve flashed messages from the session.
        flashes = session.get('_flashes', [])

        # flashes.append((category, message))
        # Clear the flashed messages from the session after retrieving them.
        session['_flashes'] = []

        # Create a JSON response from the provided keyword arguments.
        _ret_json = jsonify(kwargs)
        # Create a full response object.
        resp = make_response(_ret_json, code)
        # Prepare flashed messages to be sent in a custom header.
        flash_json = []
        for f in flashes:
            flash_json.append([f[0], f[1]])
        # Add the flashed messages as a JSON string to the response headers.
        resp.headers['api_flashes'] = json.dumps(flash_json)
        # Set the content type header.
        resp.headers['Content-Type'] = 'application/json; charset=utf-8'
        return resp

    # 配置增删改查页面标题
    # This method initializes the titles for various views (list, add, edit, show).
    def _init_titles(self):
        # Set the help URL based on the model's table name from the configuration.
        self.help_url = (
            conf.get('HELP_URL', {}).get(self.datamodel.obj.__tablename__, '')
            if self.datamodel
            else ''
        )

        """
            Init Titles if not defined
        """
        # Get the model's class name.
        class_name = self.datamodel.model_name
        # If list_title is not explicitly set, generate it.
        if not self.list_title:
            if not self.label_title:
                self.list_title = 'List ' + self._prettify_name(class_name)
            else:
                self.list_title = self.label_title + ' 列表'
        # If add_title is not explicitly set, generate it.
        if not self.add_title:
            if not self.label_title:
                self.add_title = 'Add ' + self._prettify_name(class_name)
            else:
                self.add_title = '添加 ' + self.label_title
        # If edit_title is not explicitly set, generate it.
        if not self.edit_title:
            if not self.label_title:
                self.edit_title = 'Edit ' + self._prettify_name(class_name)
            else:
                self.edit_title = '修改 ' + self.label_title
        # If show_title is not explicitly set, generate it.
        if not self.show_title:
            if not self.label_title:
                self.show_title = 'Show ' + self._prettify_name(class_name)
            else:
                self.show_title = self.label_title + ' 详情'
        # Set the main title to the list title by default.
        self.title = self.list_title

    # 每个用户对当前记录的权限，base_permissions 是对所有记录的权限
    # This method can be overridden to implement item-specific permission checks.
    def check_item_permissions(self, item):
        # By default, grant all permissions.
        self.user_permissions = {'add': True, 'edit': True, 'delete': True, 'show': True}

    # 配置字段的中文描述
    # @pysnooper.snoop()
    def _gen_labels_columns(self, list_columns):
        """
        Auto generates pretty label_columns from list of columns
        """
        # If the model itself defines 'label_columns', use them.
        if hasattr(self.datamodel.obj, 'label_columns') and self.datamodel.obj.label_columns:
            for col in self.datamodel.obj.label_columns:
                self.label_columns[col] = self.datamodel.obj.label_columns[col]

        # For any column that doesn't have a label, generate a "prettified" one.
        for col in list_columns:
            if not self.label_columns.get(col):
                self.label_columns[col] = self._prettify_column(col)

    # 获取列的中文显示
    # Helper method to get the translated label for a column.
    def lab(self, col):
        # If a custom label is defined, use it.
        if col in self.label_columns:
            return _(self.label_columns[col])
        # Otherwise, prettify the column name and translate it.
        return _(self._prettify_column(col))

    # Hook that runs before an item is deleted. Can be used for custom validation or cleanup.
    def pre_delete(self, item):
        pass

    # Method to construct the search widget for the list view.
    def _get_search_widget(self, form=None, exclude_cols=None, widgets=None):
        exclude_cols = exclude_cols or []
        widgets = widgets or {}
        # Instantiate the custom search widget with necessary parameters.
        widgets['search'] = self.search_widget(
            route_base=self.route_base,
            form=form,
            include_cols=self.search_columns,
            exclude_cols=exclude_cols,
            filters=self._filters,
            help_url=self.help_url,
        )
        return widgets

    # Method to construct the list widget.
    def _get_list_widget(
        self,
        filters,
        actions=None,
        order_column='',
        order_direction='',
        page=None,
        page_size=None,
        widgets=None,
        **args,
    ):
        """get joined base filter and current active filter for query"""
        widgets = widgets or {}
        actions = actions or self.actions
        page_size = page_size or self.page_size
        # If no ordering is specified, use the default base order.
        if not order_column and self.base_order:
            order_column, order_direction = self.base_order
        # Combine the base filters with the current filters from the UI.
        joined_filters = filters.get_joined_filters(self._base_filters)
        # Query the database to get the items and the total count.
        count, lst = self.datamodel.query(
            joined_filters,
            order_column,
            order_direction,
            page=page,
            page_size=page_size,
        )
        # Apply any post-processing to the list of items.
        if self.post_list:
            lst = self.post_list(lst)
        # Get the primary keys of the items.
        pks = self.datamodel.get_keys(lst)

        # serialize composite pks
        # Serialize primary keys, especially important for composite keys.
        pks = [self._serialize_pk_if_composite(pk) for pk in pks]

        # Instantiate the list widget with the queried data and configuration.
        widgets['list'] = self.list_widget(
            label_columns=self.label_columns,
            include_columns=self.list_columns,
            value_columns=self.datamodel.get_values(lst, self.list_columns),
            order_columns=self.order_columns,
            formatters_columns=self.formatters_columns,
            page=page,
            page_size=page_size,
            count=count,
            pks=pks,
            actions=actions,
            filters=filters,
            modelview_name=self.__class__.__name__,
        )
        return widgets

    # Log list view access.
    @event_logger.log_this
    # Expose this method at the '/list/' URL.
    @expose('/list/')
    # Require access permission.
    @has_access
    def list(self):
        # Run the pre-list hook if defined.
        if self.pre_list:
            self.pre_list()
        # Get the widgets for the list view.
        widgets = self._list()
        # Render the list template with the generated widgets and title.
        res = self.render_template(self.list_template, title=self.list_title, widgets=widgets)
        return res

    # Log show view access.
    @event_logger.log_this
    # Expose this method at '/show/<pk>'.
    @expose('/show/<pk>', methods=['GET'])
    # Require access permission.
    @has_access
    def show(self, pk):
        # Deserialize the primary key if it's composite.
        pk = self._deserialize_pk_if_composite(pk)

        # Run the pre-show hook if defined.
        if self.pre_show:
            src_item_object = self.datamodel.get(pk, self._base_filters)
            self.pre_show(src_item_object)
        # Get the widgets for the show view.
        widgets = self._show(pk)
        # Render the show template.
        return self.render_template(
            self.show_template,
            pk=pk,
            title=self.show_title,
            widgets=widgets,
            related_views=self._related_views,
        )

    # @pysnooper.snoop(watch_explode=('item'))
    def _add(self):
        """
        Add function logic, override to implement different logic
        returns add widget or None
        """
        is_valid_form = True
        # Process filter arguments from the request.
        get_filter_args(self._filters)
        # Get columns that should be excluded from the form (e.g., relations).
        exclude_cols = self._filters.get_relation_cols()
        # Refresh the add form.
        form = self.add_form.refresh()

        # Handle form submission on POST request.
        if request.method == 'POST':
            # Fill the form with any excluded columns (e.g., hidden fields).
            self._fill_form_exclude_cols(exclude_cols, form)
            # Validate the form.
            if form.validate():
                # Process the form data (e.g., custom transformations).
                self.process_form(form, True)
                # Create a new model instance.
                item = self.datamodel.obj()

                try:
                    # Populate the model instance with form data.
                    form.populate_obj(item)
                    # Run the pre-add hook.
                    self.pre_add(item)
                except Exception as e:
                    # Flash an error message if something goes wrong.
                    flash(str(e), 'danger')
                else:
                    # For debugging: print the JSON representation of the new item.
                    print(item.to_json())
                    # Add the new item to the database.
                    if self.datamodel.add(item):
                        # Run the post-add hook if the addition was successful.
                        self.post_add(item)
                    # Flash the result message from the data model.
                    flash(*self.datamodel.message)
            else:
                # Mark the form as invalid if validation fails.
                is_valid_form = False
        # If the form processing was successful, handle redirection.
        if is_valid_form:
            self.update_redirect()
        # Return the add widget to be rendered.
        return self._get_add_widget(form=form, exclude_cols=exclude_cols)

    # Log add view access.
    @event_logger.log_this
    # Expose this method at '/add'.
    @expose('/add', methods=['GET', 'POST'])
    # Require access permission.
    @has_access
    def add(self):
        # Initialize the source item JSON.
        self.src_item_json = {}
        # Handle GET requests, specifically for running the pre-add hook.
        if request.method == 'GET' and self.pre_add_get:
            try:
                # Run the pre-add GET hook.
                self.pre_add_get()
                # Re-initialize the model converter and recreate the add form.
                # This allows the hook to dynamically alter the form.
                self.conv = GeneralModelConverter(self.datamodel)
                self.add_form = self.conv.create_form(
                    self.label_columns,
                    self.add_columns,
                    self.description_columns,
                    self.validators_columns,
                    self.add_form_extra_fields,
                    self.add_form_query_rel_fields,
                )
            except Exception as e:
                # Print the error and redirect on failure.
                print(e)
                return redirect(self.get_redirect())

        # Call the internal add logic.
        widget = self._add()
        # If no widget is returned, it means the operation is complete.
        if not widget:
            # Redirect to a specific URL if defined.
            if self.check_redirect_list_url:
                return redirect(self.check_redirect_list_url)
            # Otherwise, use the standard post-add redirect.
            return self.post_add_redirect()
        else:
            # Render the add template with the add widget.
            return self.render_template(self.add_template, title=self.add_title, widgets=widget)

    # @pysnooper.snoop(watch_explode=('item'))
    def _edit(self, pk):
        """
        Edit function logic, override to implement different logic
        returns Edit widget and related list or None
        """
        is_valid_form = True
        # Get pagination and ordering arguments from the request.
        pages = get_page_args()
        page_sizes = get_page_size_args()
        orders = get_order_args()
        get_filter_args(self._filters)
        exclude_cols = self._filters.get_relation_cols()

        # Retrieve the item to be edited from the database.
        item = self.datamodel.get(pk, self._base_filters)
        # If the item doesn't exist, return a 404 error.
        if not item:
            abort(404)
        # convert pk to correct type, if pk is non string type.
        # Ensure the primary key is of the correct type.
        pk = self.datamodel.get_pk_value(item)

        # Handle form submission on POST request.
        if request.method == 'POST':
            # Refresh the edit form with data from the request.
            form = self.edit_form.refresh(request.form)

            # fill the form with the suppressed cols, generated from exclude_cols
            self._fill_form_exclude_cols(exclude_cols, form)
            # trick to pass unique validation
            # Set the form's internal ID to the item's PK to handle unique constraints correctly.
            form._id = pk
            # Validate the form.
            if form.validate():
                # Process the form data.
                self.process_form(form, False)

                try:
                    # Populate the model instance with the updated data.
                    form.populate_obj(item)
                    # Run the pre-update hook.
                    self.pre_update(item)
                except Exception as e:
                    # Flash an error message on failure.
                    flash(str(e), 'danger')
                else:
                    # Save the changes to the database.
                    if self.datamodel.edit(item):
                        # Run the post-update hook if successful.
                        self.post_update(item)
                    # Flash the result message.
                    flash(*self.datamodel.message)
            else:
                # Mark form as invalid on validation failure.
                is_valid_form = False
        else:
            # On GET request, populate the form with the item's existing data.
            # Only force form refresh for select cascade events
            form = self.edit_form.refresh(obj=item)
            # Perform additional actions to pre-fill the edit form.
            self.prefill_form(form, pk)

        # Get the edit widget.
        widgets = self._get_edit_widget(form=form, exclude_cols=exclude_cols)
        # Get widgets for any related views.
        widgets = self._get_related_views_widgets(
            item,
            filters={},
            orders=orders,
            pages=pages,
            page_sizes=page_sizes,
            widgets=widgets,
        )
        # If form processing was successful, handle redirection.
        if is_valid_form:
            self.update_redirect()
        return widgets

    # Log edit view access.
    @event_logger.log_this
    # Expose this method at '/edit/<pk>'.
    @expose('/edit/<pk>', methods=['GET', 'POST'])
    # Require access permission.
    @has_access
    def edit(self, pk):
        # Deserialize the primary key.
        pk = self._deserialize_pk_if_composite(pk)
        # Get the source object being edited.
        self.src_item_object = self.datamodel.get(pk, self._base_filters)

        # Handle GET requests, specifically for the pre-update hook.
        if request.method == 'GET' and self.pre_update_get and self.src_item_object:
            try:
                # Run the pre-update GET hook.
                self.pre_update_get(self.src_item_object)
                # Re-initialize the model converter and form to allow dynamic changes.
                self.conv = GeneralModelConverter(self.datamodel)
                # 重新更新，而不是只在初始化时更新
                self.edit_form = self.conv.create_form(
                    self.label_columns,
                    self.edit_columns,
                    self.description_columns,
                    self.validators_columns,
                    self.edit_form_extra_fields,
                    self.edit_form_query_rel_fields,
                )
            except Exception as e:
                # On error, print and redirect.
                print(e)
                self.update_redirect()
                return redirect(self.get_redirect())

        # If the item exists, store its JSON representation.
        if self.src_item_object:
            self.src_item_json = self.src_item_object.to_json()

        # if self.check_redirect_list_url:
        try:
            # If a custom edit permission check is defined, run it.
            if self.check_edit_permission:
                has_permission = self.check_edit_permission(self.src_item_object)
                # If the user does not have permission, redirect them.
                if not has_permission:
                    self.update_redirect()
                    url = self.get_redirect()
                    return redirect(url)

        except Exception as e:
            # On permission check error, flash a warning and redirect.
            print(e)
            flash(str(e), 'warning')
            self.update_redirect()
            return redirect(self.get_redirect())
            # return redirect(self.check_redirect_list_url)

        # Call the internal edit logic.
        widgets = self._edit(pk)

        # If no widget is returned, the operation is complete, so redirect.
        if not widgets:
            return self.post_edit_redirect()
        else:
            # Render the edit template with the widgets.
            return self.render_template(
                self.edit_template,
                title=self.edit_title,
                widgets=widgets,
                related_views=self._related_views,
            )

    # Log delete action.
    @event_logger.log_this
    # Expose this method at '/delete/<pk>'.
    @expose('/delete/<pk>')
    # Require access permission.
    @has_access
    def delete(self, pk):
        # Deserialize the primary key.
        pk = self._deserialize_pk_if_composite(pk)
        # Get the object to be deleted.
        self.src_item_object = self.datamodel.get(pk, self._base_filters)
        # Store its JSON representation for logging/auditing purposes.
        if self.src_item_object:
            self.src_item_json = self.src_item_object.to_json()
        # Perform custom permission check before deleting.
        if self.check_redirect_list_url:
            try:
                if self.check_edit_permission:
                    # If permission is denied, flash a message and redirect.
                    if not self.check_edit_permission(self.src_item_object):
                        flash('no permission delete', 'warning')
                        return redirect(self.check_redirect_list_url)
            except Exception as e:
                # On error, flash a message and redirect.
                print(e)
                flash(str(e), 'warning')
                return redirect(self.check_redirect_list_url)
        # Call the internal delete logic.
        self._delete(pk)
        # Redirect back to the list view after deletion.
        url = url_for(f'{self.endpoint}.list')
        return redirect(url)
        # return self.post_delete_redirect()


# A mixin to create a compact CRUD interface where list, add, and edit are on one page.
class CompactCRUDMixin(BaseCRUDView):
    """
    Mix with ModelView to implement a list with add and edit on the same page.
    """

    @classmethod
    def set_key(cls, k, v):
        """Allows attaching stateless information to the class using the
        flask session dict
        """
        # Create a unique session key for this class.
        k = cls.__name__ + '__' + k
        session[k] = v

    @classmethod
    def get_key(cls, k, default=None):
        """Matching get method for ``set_key``"""
        # Create the unique session key.
        k = cls.__name__ + '__' + k
        # Retrieve the value from the session, returning a default if not found.
        if k in session:
            return session[k]
        else:
            return default

    @classmethod
    def del_key(cls, k):
        """Matching get method for ``set_key``"""
        # Create the unique session key.
        k = cls.__name__ + '__' + k
        # Remove the key from the session.
        session.pop(k)

    # Override the list widget getter to include the add/edit form.
    def _get_list_widget(self, **args):
        """get joined base filter and current active filter for query"""
        # Get the standard list widget first.
        widgets = super()._get_list_widget(**args)
        # Check the session to see if an add or edit form should be displayed.
        session_form_widget = self.get_key('session_form_widget', None)

        form_widget = None
        # If the session indicates 'add', get the add form widget.
        if session_form_widget == 'add':
            form_widget = self._add().get('add')
        # If the session indicates 'edit', get the edit form widget for the specified PK.
        elif session_form_widget == 'edit':
            pk = self.get_key('session_form_edit_pk')
            if pk and self.datamodel.get(int(pk)):
                form_widget = self._edit(int(pk)).get('edit')
        # Return a special GroupFormListWidget that combines the list and the form.
        return {
            'list': GroupFormListWidget(
                list_widget=widgets.get('list'),
                form_widget=form_widget,
                form_action=self.get_key('session_form_action', ''),
                form_title=self.get_key('session_form_title', ''),
            )
        }

    # Expose the list view.
    @expose('/list/', methods=['GET', 'POST'])
    @has_access
    def list(self):
        # Get the combined list/form widgets.
        list_widgets = self._list()
        # Render the template.
        return self.render_template(
            self.list_template, title=self.list_title, widgets=list_widgets
        )

    # Expose the delete action.
    @expose('/delete/<pk>')
    @has_access
    def delete(self, pk):
        pk = self._deserialize_pk_if_composite(pk)
        # Perform the deletion.
        self._delete(pk)
        # If the deleted item was the one being edited, clear its PK from the session.
        edit_pk = self.get_key('session_form_edit_pk')
        if pk == edit_pk:
            self.del_key('session_form_edit_pk')
        # Redirect back to the list.
        return redirect(self.get_redirect())


# 可以多选的列表页面
# Custom list widget that includes checkboxes for each row.
class ListWidgetWithCheckboxes(ListWidget):
    """An alternative to list view that renders Boolean fields as checkboxes

    Works in conjunction with the `checkbox` view."""

    # Use a custom template that includes checkboxes.
    template = 'myapp/fab_overrides/list_with_checkboxes.html'


# A WTForms validator to ensure a field contains valid JSON.
def validate_json(form, field):  # noqa
    try:
        # Try to load the field's data as JSON.
        json.loads(field.data)
    except Exception as e:
        # If it fails, log the error and raise a validation exception.
        logging.exception(e)
        raise Exception(_("json isn't valid"))


# A mixin to add a "Export to YAML" action to a ModelView.
class YamlExportMixin:
    # Define the action decorator.
    @action('yaml_export', __('Export to YAML'), __('Export to YAML?'), 'fa-download')
    def yaml_export(self, items):
        # Ensure 'items' is a list.
        if not isinstance(items, list):
            items = [items]

        # Convert each item to a dictionary.
        data = [t.export_to_dict() for t in items]
        # Return a response containing the YAML data.
        return Response(
            yaml.safe_dump(data),
            # Set headers to trigger a download.
            headers=generate_download_headers('yaml'),
            mimetype='application/text',
        )


# 列表页面删除/批量删除的操作
# A mixin that provides single and bulk delete functionality.
class DeleteMixin:
    def _delete(self, pk):
        """
        Delete function logic, override to implement diferent logic
        deletes the record with primary_key = pk

        :param pk:
            record primary key to delete
        """
        # Get the item to be deleted.
        item = self.datamodel.get(pk, self._base_filters)
        # Abort if the item does not exist.
        if not item:
            abort(404)
        try:
            # Run the pre-delete hook.
            self.pre_delete(item)
        except Exception as e:
            # Flash an error if the hook fails.
            flash(str(e), 'danger')
        else:
            # Find the security permissions associated with this item.
            view_menu = security_manager.find_view_menu(item.get_perm())
            pvs = (
                security_manager.get_session.query(security_manager.permissionview_model)
                .filter_by(view_menu=view_menu)
                .all()
            )

            # Handle additional schema-related permissions if they exist.
            schema_view_menu = None
            if hasattr(item, 'schema_perm'):
                schema_view_menu = security_manager.find_view_menu(item.schema_perm)

                pvs.extend(
                    security_manager.get_session.query(security_manager.permissionview_model)
                    .filter_by(view_menu=schema_view_menu)
                    .all()
                )

            # Delete the item from the database.
            if self.datamodel.delete(item):
                # Run the post-delete hook.
                self.post_delete(item)

                # Clean up all related permission objects from the security database.
                for pv in pvs:
                    security_manager.get_session.delete(pv)

                if view_menu:
                    security_manager.get_session.delete(view_menu)

                if schema_view_menu:
                    security_manager.get_session.delete(schema_view_menu)

                # Commit the security changes.
                security_manager.get_session.commit()

            # Flash the result message and update the redirect URL.
            flash(*self.datamodel.message)
            self.update_redirect()

    # Define a bulk delete action.
    @action('muldelete', __('Delete'), __('Delete all Really?'), 'fa-trash', single=False)
    def muldelete(self, items):
        # Abort if no items are selected.
        if not items:
            abort(404)
        # Iterate over the selected items.
        for item in items:
            try:
                # Run the pre-delete hook for each item.
                self.pre_delete(item)
            except Exception as e:
                # Flash an error if the hook fails.
                flash(str(e), 'danger')
            else:
                # Call the internal delete method for each item.
                self._delete(item.id)
        # Update and perform the redirect after all items are processed.
        self.update_redirect()
        return redirect(self.get_redirect())


# model list的过滤器
# A custom filter class with added utility functions for permission checking.
class MyappFilter(BaseFilter):
    """Add utility function to make BaseFilter easy and fast

    These utility function exist in the SecurityManager, but would do
    a database round trip at every check. Here we cache the role objects
    to be able to make multiple checks but query the db only once
    """

    # Helper to get the current user's roles.
    def get_user_roles(self):
        return get_user_roles()

    # Get a set of all permissions for the current user.
    def get_all_permissions(self):
        """Returns a set of tuples with the perm name and view menu name"""
        perms = set()
        # Iterate through the user's roles and their associated permissions.
        for role in self.get_user_roles():
            for perm_view in role.permissions:
                t = (perm_view.permission.name, perm_view.view_menu.name)
                perms.add(t)
        return perms

    # Check if the user has a specific role.
    def has_role(self, role_name_or_list):
        """Whether the user has this role name"""
        # Coerce a single role name into a list.
        if not isinstance(role_name_or_list, list):
            role_name_or_list = [role_name_or_list]
        # Check if any of the user's roles match the provided names.
        return any(r.name in role_name_or_list for r in self.get_user_roles())

    # Check if the user has a specific permission on a specific view.
    def has_perm(self, permission_name, view_menu_name):
        """Whether the user has this perm"""
        return (permission_name, view_menu_name) in self.get_all_permissions()

    # 获取所有绑定了指定权限的所有vm
    def get_view_menus(self, permission_name):
        """Returns the details of view_menus for a perm name"""
        vm = set()
        # Find all view menus where the user has the specified permission.
        for perm_name, vm_name in self.get_all_permissions():
            if perm_name == permission_name:
                vm.add(vm_name)
        return vm


# 检查是否有权限
# A utility function to check if the current user owns a database object.
def check_ownership(obj, raise_if_false=True):
    """Meant to be used in `pre_update` hooks on models to enforce ownership

    Admin have all access, and other users need to be referenced on either
    the created_by field that comes with the ``AuditMixin``, or in a field
    named ``owners`` which is expected to be a one-to-many with the User
    model. It is meant to be used in the ModelView's pre_update hook in
    which raising will abort the update.
    """
    # If the object is null, ownership check fails.
    if not obj:
        return False

    # Define the exception to be raised on failure.
    security_exception = Exception(f"You don't have the rights to alter [{obj}]")

    # Anonymous users never have ownership.
    if g.user.is_anonymous:
        if raise_if_false:
            raise security_exception
        return False
    # Users with the 'Admin' role have ownership of everything.
    roles = [r.name for r in get_user_roles()]
    if 'Admin' in roles:
        return True
    # Create a new database session to get the original object state.
    session = db.create_scoped_session()
    orig_obj = session.query(obj.__class__).filter_by(id=obj.id).first()

    # Making a list of owners that works across ORM models
    # Collect a list of all possible owners from different fields.
    owners = []
    if hasattr(orig_obj, 'owners'):
        owners += orig_obj.owners
    if hasattr(orig_obj, 'owner'):
        owners += [orig_obj.owner]
    if hasattr(orig_obj, 'created_by'):
        owners += [orig_obj.created_by]

    # Get the usernames of the owners.
    owner_names = [o.username for o in owners if o]

    # Check if the current user's username is in the list of owners.
    if g.user and hasattr(g.user, 'username') and g.user.username in owner_names:
        return True
    # If the check fails, either raise an exception or return False.
    if raise_if_false:
        raise security_exception
    else:
        return False


# 绑定字段
# A custom field binding function for WTForms.
def bind_field(
    self, form: DynamicForm, unbound_field: UnboundField, options: dict[Any, Any]
) -> Field:
    """
    Customize how fields are bound by stripping all whitespace.

    :param form: The form
    :param unbound_field: The unbound field
    :param options: The field options
    :returns: The bound field
    """

    # Get existing filters or initialize an empty list.
    filters = unbound_field.kwargs.get('filters', [])
    # Add a new filter that strips whitespace from string inputs.
    filters.append(lambda x: x.strip() if isinstance(x, str) else x)
    # Bind the field with the updated filters.
    return unbound_field.bind(form=form, filters=filters, **options)


# Monkey-patch the FlaskForm meta class to use the custom bind_field function globally.
FlaskForm.Meta.bind_field = bind_field
